#pragma once

#include "HpcServer/Server/Logger.hpp"
#include "means.hpp"

using namespace LogMudule;
using namespace std;

// 倒排索引节点
struct InvertedNode
{
    // 关键字
    string _keyword;
    // 网页文档ID
    uint64_t _doc_id;
    // 网页权重
    uint64_t _weight;
};

struct DocInfo
{
    string _title;    // 网页标题
    string _content;  // 网页有效内容
    string _url;      // 网页URL
    uint64_t _doc_id; // 网页文档ID
};


class Index
{
    static const uint32_t title_word_weight = 50;  // 出现在网页标题中的关键字所占的权值
    static const uint32_t content_word_weight = 1; // 出现在网页正文中的关键字所占的权值

private:
    // 构建正排索引
    DocInfo *BuildForwardIndex(const string &line)
    {
        DocInfo doc;
        vector<string> tmp;
        int num=0;
        if ((num=means::SepStr(line, DocLineSep, tmp)) != 3)
        {
            return nullptr;
        }
        // 网页标题
        doc._title = tmp[0];
        // 网页正文
        doc._content = tmp[1];
        // 网页URL
        doc._url = tmp[2];
        // 计算文档ID,以下标作为ID
        doc._doc_id = _forward_index.size();
        // 构建索引
        _forward_index.push_back(move(doc));

        return &_forward_index.back();
    }
    // 构建倒排索引
    bool BuildInvertedIndex(const DocInfo &doc)
    {
        // 词频统计
        struct word_cnt
        {
            uint64_t title_cnt;   // 词在标题中出现的次数
            uint64_t content_cnt; // 词在正文中出现的次数

            word_cnt() : title_cnt(0), content_cnt(0)
            {
            }
        };

        // 分词
        vector<string> title_words;
        vector<string> content_words;
        means::CppjiebaCatStr(doc._title, &title_words);
        means::CppjiebaCatStr(doc._content, &content_words);

        // 使用哈希表统计关键词出现的次数
        unordered_map<string, word_cnt> keywords;

        // 统计标题中的关键字的出现的次数
        for (string word : title_words)
        {
            // 统一转换成小写
            boost::to_lower(word);

            keywords[word].title_cnt++;
        }

        // 统计正文中的关键字的出现的次数
        for (string word : content_words)
        {
            // 统一转换成小写
            boost::to_lower(word);

            keywords[word].content_cnt++;
        }

        // 遍历词频统计的哈希表
        for (auto &element : keywords)
        {
            // 一个关键字，建立一个关于该文档的InvertedNode
            InvertedNode node;
            node._doc_id = doc._doc_id; // 文档ID

            node._keyword = element.first; // 关键字

            // 简单计算权重(相关性)
            node._weight = (element.second.title_cnt * title_word_weight) +
                           (content_word_weight * element.second.content_cnt);

            // 建立索引
            _inverted_index[element.first].push_back(move(node));
        }

        return true;
    }

    Index()
    {
    }
    Index(const Index &obj) = delete;
    Index &operator=(const Index &obj) = delete;

    static Index *_This; // 单例化之后，指向自己唯一的那个对象
    static mutex _mutex; // 保护GetIndexObj函数，防止出现线程安全问题
public:
    static Index *GetIndexObj()
    {
        if (_This == nullptr)
        {
            // 加锁
            lock_guard<mutex> lock(_mutex);
            if (_This == nullptr)
            {
                _This = new Index();
            }
        }
        return _This;
    }
    // 通过正排索引，使用文档ID找到文档内容
    DocInfo *GetForwardIndex(uint64_t id)
    {
        // 如果下标越界
        if (id >= _forward_index.size())
        {
            LOG(LogLevel::WARNING) << "文档id: " << id << "对应的文档不存在!";
            return nullptr;
        }

        return &_forward_index[id];
    }

    // 通过倒排索引，使用关键字找到一个（多个）具体的倒排索引节点
    vector<InvertedNode>* GetInvertedIndex(const string &keyword)
    {
        // 如果没有关键字
        if (_inverted_index.find(keyword) == _inverted_index.end())
        {
            LOG(LogLevel::WARNING) << "关键字: " << keyword << "对应的文档不存在!";
            return nullptr;
        }

        return &_inverted_index[keyword];
    }
    // 根据处理后的网页文件内容，构建正排和倒排索引
    bool BuildIndex(const string &parser_file_path)
    {
        // 按行读取文件
        ifstream ifs(parser_file_path, ios::in | ios::binary);
        if (ifs.is_open() == false)
        {
            LOG(LogLevel::WARNING) << "文件: " << parser_file_path << "打开失败!";
            return false;
        }

        string line;
        uint64_t count=0;
        while (getline(ifs, line))
        {
            // 构建正排索引
            DocInfo *doc = BuildForwardIndex(line);
            // 如果构建失败
            if (doc == nullptr)
            {
                continue;
            }

            // 构建倒排索引
            if (BuildInvertedIndex(*doc) == false)
            {
                continue;
            }
            count++;
            if(count%100==0)
            {
                LOG(LogLevel::DEBUG)<<"构建正排和倒排索引成功的文档个数: "<<count;
            }
        }

        // 关闭文件
        ifs.close();
        return true;
    }
    ~Index()
    {
    }

private:
    // 正排索引：文档ID映射具体文档,以数组下标为文档ID
    // 因为是一直尾插，不会删除元素，所以用数组即可
    vector<DocInfo> _forward_index;
    // 倒排索引：一个关键字映射一个（多个）具体的倒排索引节点
    // （因为一个关键字可能在多个文档中出现）
    unordered_map<string, vector<InvertedNode>> _inverted_index;
};

Index *Index::_This = nullptr;
mutex Index::_mutex;

